<?php


class Zend_Db_ActiveRecord_Gateway
{
	protected static $_instances = array();
	
	protected $_className;
	
	/**
	 * @var Zend_Db_ActiveRecord_Abstract
	 */
	protected $_obj;
	
	/**
	 * Zend_Db_Adapter_Abstract object.
	 *
	 * @var Zend_Db_Adapter_Abstract
	 */
	protected $_db;
	
	/**
	 * Instantiates gateway for given class
	 *
	 * @param string $className Class name for the active record object this
	 * gateway will represent
	 * @return Zend_Db_ActiveRecord_Gateway
	 */
	public static function getInstance($className)
	{
		if (key_exists($className, self::$_instances)) {
			return self::$_instances[$className];
		}
		
		return new self($className);
	}

	/**
	 * Constructs a new Gateway for the given active record class.
	 * 
	 * @param string $className The name of the class the gateway will work with.
	 */
	public function __construct($className)
	{
		$this->_className = $className;
		if (key_exists($className, self::$_instances)) {
			$this->_obj = self::$_instances[$className]->_obj;
		} else {
			self::$_instances[$className] = $this;
			$this->_obj = new $className();
		}
		
		$this->_db = $this->_obj->getAdapter();
	}
	
	/**
     * Creates and returns a new Zend_Db_Select object for this gateway.
     *
     * @return Zend_Db_Select
     */
	public function select()
	{
		return $this->_obj->select();
	}

	/**
	 * Returns first object found based on parameters, may be an id provided
	 *
	 * @param int|string|array|Zend_Db_Select $id_or_where
	 * @return Zend_Db_ActiveRecord_Abstract
	 */
	public function fetch($id_or_where = null)
	{
		$select = $this->_createSelect($id_or_where);
		$select->limit(1);
		$result = $this->fetchAll($select);
		return count($result) ? $result[0] : null;
	}

	/**
	 * Returns array of objects based on parameters
	 * 
	 * @param string|array|Zend_Db_Select $where
	 * @return array
	 */
	public function fetchAll($where = null)
	{
		$select = $this->_createSelect($where);
		
		if (!$select->getPart('from')) {
			$tableName = $this->_obj->inflect('table');
			$select->from($tableName);
		}
		
		$result = $this->_db->query($select)->fetchAll(Zend_Db::FETCH_ASSOC);
		
		foreach ($result as $key => $row) {
			$result[$key] = new $this->_className($row);
		}
		return $result;
	}

	/**
	 * Returns whether or not the object exists in the database
	 * 
	 * @param int|string|array|Zend_Db_Select $id_or_where
	 * @return boolean
	 */
	public function exists($id_or_where = null)
	{
		$select = $this->_createSelect($id_or_where);
		return ($this->count($select) > 0);
	}

	/**
	 * Creates new object, populates the attributes from the array, 
	 * saves it if it validates, and returns it
	 *
	 * @param array $properties
	 * @return Zend_Db_ActiveRecord_Abstract
	 */
	public function create(array $properties = null)
	{
		$obj = new $this->_className();
		$obj->setProperties($properties);
		$obj->save();
		return $obj;
	}

	/**
	 * Updates an object already stored in the database with the properties passed
	 * 
	 * @param array $properties
	 * @param int|string|array|Zend_Db_Select $id_or_where
	 * @return boolean Whether it was successfully updated
	 */
	public function update(array $properties, $id_or_where = null)
	{
		$select = $this->_createSelect($id_or_where);
		$where = $this->_extractWhere($select);
		
		$tableName = $this->_obj->inflect('table');
		
		$fields = array();
		foreach ($properties as $key => $value) {
			$key = $this->_obj->inflect('field', array('propertyName' => $key));
			$fields[$key] = $value;
		}
		
		$success = ($this->_db->update($tableName, $fields, $where) > 0);
		return $success;
	}

	/**
	 * Delete object by id
	 *
	 * @param int|string|array|Zend_Db_Select $id_or_where
	 * @return boolean Whether object was deleted
	 */
	public function delete($id_or_where = null)
	{
		$select = $this->_createSelect($id_or_where);
		$where = $this->_extractWhere($select);
		
		$tableName = $this->_obj->inflect('table');
		$result = $this->_db->delete($tableName, $where);
		return $result;
	}

	/**
	 * Returns the number of records that meet the conditions
	 *
	 * @param string|array|Zend_Db_Select $where
	 * @return int
	 */
	public function count($where = null)
	{
		$select = clone $this->_createSelect($where);
		
		$tableName = $this->_obj->inflect('table');
		if (!$select->getPart('columns')) {
			$select->from($tableName, 'COUNT(*)');
		} else {
			$select = Zend_Db_ActiveRecord_Select::cloneCountSelect($select);
		}
		return $this->_db->fetchOne($select);
	}

	/**
	 * Increment a property in an ActiveRecord row
	 * 
	 * @param string $counter The property of the class to be incremented
	 * @param int|string|array|Zend_Db_Select $id_or_where
	 */
	public function incrementCounter($counter, $id_or_where = null)
	{
		$counterField = $this->_obj->inflect('field', array('propertyName' => $counter));
		$this->update(array($counter => "$counterField + 1"), $id_or_where);
	}

	/**
	 * Decrements a counter in a record
	 *
	 * @param string $counter The property of the class to be decremented
	 * @param int|string|array|Zend_Db_Select $id_or_where
	 */
	public function decrementCounter($counter, $id_or_where = null)
	{
		$counterField = $this->_obj->inflect('field', array('propertyName' => $counter));
		$this->update(array($counter => "$counterField - 1"), $id_or_where);
	}
	
	/**
	 * Gets the Zend_Db_Adapter_Abstract for this particular gateway object.
	 *
	 * @return Zend_Db_Adapter_Abstract
	 */
	public function getAdapter()
	{
		return $this->_db;
	}
	
	
	public function inflect($inflection, array $values = array())
	{
		return $this->_obj->inflect($inflection, $values);
	}
	
	/**
	 * Returns all the columns for this type of active record for use in select
	 * objects.
	 * 
	 * @return array An array of column names
	 */
	public function getColumns()
	{
		return array_keys($this->_obj->getMetadata());
	}
	
	
	/**
	 * Returns a new instance of the active record type this gateway represents.
	 * 
	 * @return Zend_Db_ActiveRecord_Abstract An unsaved, unmodified active
	 * record instance.
	 */
	public function getNewInstance()
	{
		return new $this->_className();
	}

	/**
	 * Takes input and creates a select object with it.
	 * 
	 * @param int|string|array|Zend_Db_Select $id_or_where
	 * @return Zend_Db_Select
	 */
	protected function _createSelect($id_or_where)
	{
		if ($id_or_where instanceof Zend_Db_Select) {
			$select = $id_or_where;
		} elseif (is_int($id_or_where) || (is_string($id_or_where) && strpos($id_or_where, ' ') === false)) {
			// this is an id, a single int or string value with no spaces
			$pk = $this->inflect('primaryKey');
			$tableName = $this->inflect('table');
			$select = $this->select()->where("$tableName.$pk = ?", $id_or_where);
		} elseif ($id_or_where != null) {
			$select = $this->select();
			$this->_where($select, $id_or_where);
		} else {
			$select = $this->select();
		}
		
		return $select;
	}

	/**
	 * Generate WHERE clause from user-supplied string or array
	 *
	 * @param  string|array $where  OPTIONAL An SQL WHERE clause.
	 * @return Zend_Db_Select
	 */
	protected function _where($select, $where)
	{
		$where = (array) $where;
		
		foreach ($where as $key => $val) {
			// is $key an int?
			if (is_int($key)) {
				// $val is the full condition
				$select->where($val);
			} else {
				// $key is the condition with placeholder,
				// and $val is quoted into the condition
				$select->where($key, $val);
			}
		}
		
		return $select;
	}

	/**
	 * Generate WHERE clause from user-supplied string or array
	 *
	 * @param  string|array $where  OPTIONAL An SQL WHERE clause.
	 * @return Zend_Db_Select
	 */
	protected function _extractWhere(Zend_Db_Select $select)
	{
		$select->from('dummy');
		return substr($select, strpos($select, ' WHERE') + strlen(' WHERE'));
	}
}